Что такое OAuth 2.0 и OIDC
Различие протоколов OAuth 2.0 и OIDC
OAuth 2.0 позволяет выполнять запросы к серверу от имени пользователя. Это только API. Мы можем вызвать эндпоинт /me. Но это лишний запрос и не даёт гарантию проверки, как это происходит при проверке JWT id токена, к тому же OIDC централизованное решение, которое применимо для всех серверов авторизации в отличии от различных эндпоинтов
OIDC даёт нам понимание, кто именно вошёл в систему - с помощью поля sub (уникальный идентификатор клиента - subject). Также могут быть добавлены другие поля - почта, имя, телефон в зависимости от запроса
OAuth 2.0 - Авторизация | OIDC - Аутентификация
Где в мире используется OAuth 2.0 и OIDC Практическое использование. Примеры
OAuth 2.0
https://auth.example.com/oauth/authorize?
response_type=code
&client_id=abc123
&redirect_uri=https://client.example.com/callback
&scope=read write
&state=xyz987
Используется при работе с API. Например, я разработал приложение для работы с музыкой и мне надо получить все песни пользователя из Яндекс музыки. Чтобы не логиниться через браузер от имени пользователя, а пользователю сохранить свои данные в безопасности, он выписывает нам API токен, с помощью которого мы можем обратиться к Яндекс музыке и получить список плейлистов.
Как это происходит. На нашем сайте пользователь выбирает пункт - получить песни из Яндекс музыки. После этого пользователя перенаправляет на страницу входа в Яндекс, пользователь входит и подтверждает делегирование прав нашему сайту от имени Яндекс Музыки. Яндекс сверяет client_id приложения, и если всё верно - выдаёт authorization_code нашему пользователю, путём переадресации на наш сайт: сайт.рф?authorization_code=wdfseuknef... Далее Наш сервер видит этот параметр, отправляет его серверу Авторизации Яндекса с использованием client_secret, client_id и authorization_code. Яндекс Видит, что всё корректно и выдаёт нашему серверу access_token - способ взаимодействия с API Яндекс Музыки, а также refresh_token - если срок действия access_token истечёт, то мы можем повторно его запросить без пользователя и процедуры авторизации. Теперь мы можем с помощью access_token запросить все композиции пользователя - api.music.yandex.ru/get_music.
https://client.example.com/callback?
code=SplxlOBeZQQYbYS6WxSbIA
&state=xyz987
{
"access_token": "ya29.a0AfH6S...",
"expires_in": 3600,
"token_type": "Bearer",
"refresh_token": "1//09rU...",
"id_token": "eyJhbGciOi..." // если был scope openid
}
OIDC
https://auth.yandex.com/oauth/authorize?
response_type=code
&client_id=abc123
&redirect_uri=https::/client.example.com/oidc_callback
&scope=openid profile email
&state=xyz987
&nonce=abc456
Другая ситуация - нам надо организовать вход в наше приложение, но чтобы упростить эту процедуру, мы используем вход от Сбербанка по SberID. Полностью повторяется ситуация с OAuth 2.0, но когда нам приходит access_token, мы получаем ещё и id_token в формате JWT. С его помощью мы можем: Проверить, что токен действительно легитимен, Получить уникальный идентификатор для пользователя sub, Не требовать от пользователя дополнительных данных для входа. Формат id_token (из JWT)
{
"iss": "https://auth.yandex.com/oauth/authorize", // Кто выдал токен
"sub": "109876543210987654321", // Уникальный ID пользователя
"aud": "client-id-123", // ID клиента Именно приложения
"exp": 1718000000, // Срок действия (время UNIX)
"iat": 1717996400, // Время выпуска токена
"email": "user@example.com", // Почта (если была запрошена email)
"name": "Alexandr", // Имя (если был запрошен profile)
"nonce": "xyz..." // Подтверждает, что токен с переданным параметром
"email_verified": true // Подтверждение почты
}
Общие компоненты в данных протоколах. Понимание что зачем нужно
Общие компоненты:
Authorization Server
Осуществляет аутентификацию клиента, отвечает за безопасность всей системы. Выдаёт токены - access token и id token. Изначально это обычный ресурс, к которому есть доступ у клиента с помощью логина, пароля и двухфакторки. У этого сервера есть интеграция с некоторыми ресурсами, которые ему доверяют (Resource Server). Также у Authorization Server есть возможность регистрации клиентов (Client). Authorization Server нужен для надёжной авторизации и аутентификации.
Client / RP - Relying Party
Приложение, которому нужны доступы к API/Аутентификации. Регистрируется в Authorization Server и получает client_id, client_secret. С их помощью на беке обеспечивается безопасный обмен с Authorization Server authorization code на access_token и/или id_token
Resource owner / End-User
Пользователь, у которого есть доступ к Authorization Server и который хочет войти в Client через него, либо дать Client доступ к API.
JWKS Endpoint
Позволяет проверять подписи токенов. База открытых ключей, которыми подписываются access_token и id_token
Resourse Server
Предоставляет API ресурсы для Client. Общается с Authorization Server для проверки access token, либо проверяет его через JWT токен посредством подписи
Не общие:
IdP (Identity Provider) | OIDC, SAML
Провайдер аутентификации - приложение, которое осуществляет вход и подтверждает Authorization Server, что человек действительно авторизовался. Обычно являются единой частью с Authorization Server
OP (OpenID Provider) | Специфично для OIDC
Конкретная реализация IdP по стандарту OpenID Connect
Token Introspection Endpoint | OAuth2.0
Специальный endpoint для Resource Server, чтобы проверить, валиден ли access_token в случаях, когда используется opaque, а не JWT
UserInfo Endpoint | OIDC
Endpoint, который возвращает дополнительные сведения о пользователе по access_token
Виды и примеры атак на данные протоколы и методы митигации рисков
OAuth 2.0
Практика
НЕБЕЗОПАСНЫЕ FLOW
Direct access grants для Public Client
Самый простой способ авторизации. Происходит передача логина и пароля от Authorization Server через Client. Является самым небезопасным способом авторизации. Пример отправки
{
"grant_type": "password"
"client_id": "my-client"
"username": "someuser"
"password": "SuperSecret123"
}
То есть здесь идёт передача пароля и логина от Authorization Server напрямую через Client - дополнительный канал утечки ПД
В ответ приходит классический Access Token
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR...",
"expires_in": 300,
"token_type": "Bearer"
}
Implict Flow для Public Client
Простой способ авторизации. Пользователь хочет получить доступ к API. Для этого он переходит по ссылке для аутентификации в Authorization Server. Пример ссылки: https://auth.example.com/authorize?
response_type=token&
client_id=client_123&
redirect_uri=https://app.example.com/callback&
scope=profile email&
state=abc123
Здесь response_type - то, что мы хотим получить от Authorization Server, а именно access_token. client_id - ID приложения, которое зарегистрировано в Authorization Server, redirect_uri - то, куда будет перенаправлен пользователь с токеном после авторизации. scope - права, которые делегирует Resourse Owner данному Client для доступа к Resource Server
После чего мы проходим авторизацию на Authorization Server и получаем в URL-фрагменте (#) наш access_token:
https://app.example.com/callback#
access_token=eyJ...abc
&token_type=Bearer
&expires_in=3600
&state=abc123
1 Шаг - Страница в веб браузере без backend/мобильном приложении (Public Client) перенаправляет пользователя на страницу авторизации
2 Шаг Пользователь (Resource Owner) осуществляет авторизацию на сервере авторизации (Authorization Server)
3 Шаг Authorization Server перенаправляет нас на redirect_uri с параметром access_token
4 Шаг Public Client может общаться с Resource Server по API, используя полученный access_token
Данный способ признан небезопасным из-за передачи access_token в самом теле GET. Этот метод может логироваться, виден при передаче через HTTPS и может быть скомпрометирован. Также здесь нет возможности использовать refresh_token как в других способах. Из-за этих ограничений жизнь access_token ограничивается 15 минутами, а по истечении придётся заново авторизовываться
Authorization Code Flow (Standard Flow) для Public Client
Отличается наличием кода, который Client обменивает на access_token через POST канал, его называет authorization_code
Публичное приложение перенаправляет пользователя на страницу авторизацию, но в ссылке указан тип не token, а code. Пример ссылки:
https://auth.example.com/authorize?
response_type=code&
client_id=client_123&
redirect_uri=https://app.example.com/callback&
scope=profile email&
state=abc123
После авторизации, Authorization Server не выдаёт access_token, а выдаёт authorization_code, который впоследствии Public Client обменивает через POST запрос к Authorization Server на действующий access_token.
https://app.example.com/callback?code=GWIXNQLX&state=abc123
Теперь Public Client по защищённому методу POST передаёт authorization_code и получает в ответ access_token. Таким образом access_token не будет показан в URI, в сетевых запросах или в логах
Request:
{
"authorization_code": "GWIXNQLX"
}
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5...",
"token_type": "Bearer",
"expires_in": 3600
}
1 Шаг - Страница в веб браузере без backend/мобильном приложении (Public Client) перенаправляет пользователя на страницу авторизации
2 Шаг Пользователь (Resource Owner) осуществляет авторизацию на сервере авторизации (Authorization Server)
3 Шаг Authorization Server перенаправляет нас на redirect_uri с параметром authorization_code
4 Шаг Public Client через POST запрос передаёт authorization_code к Authorization Server
5 Шаг Authorization Server отправляет POST ответ, в котором содержится access_token
6 Шаг Public Client может общаться с Resource Server по API, используя полученный access_token
Однако, данный способ также не является безопасным и запрещён к использованию: authorization_code может быть перехвачен злоумышленником и отправлен в Authorization Server, из-за чего access_token будет получен третьей стороной
БЕЗОПАСНЫЕ FLOW
Authorization Code Flow + PKCE (Standard flow) для Public Client
PKCE расшифровывается как Proof Key for Code Exchange
Все шаги идентичны обычному Authorization Code Flow, но теперь Public Client генерирует у себя на стороне специальный code_verifier. Например: lqoctaktzu. Данный код будет служить доказательством для Authorization Server, что именно клиент запрашивает access_token. Но изначально передаётся не сам code_verifier, а его hash с указанием алгоритма - code_challenge. Обычно это SHA-256, что также указывается в параметрах с помощью поля code_challenge_method. Пример ссылки:
https://auth.example.com/authorize?
response_type=code&
client_id=client_123&
redirect_uri=https://app.example.com/callback&
scope=profile email&
state=abc123&
code_challenge=e98c4521031e10a8513...&
code_challenge_method=S256
Далее мы также получаем в ответ authorization_code
https://app.example.com/callback?code=GWIXNQLX&state=abc123
И на конечном этапе отправляется POST запрос, к которому дополнительно прикрепляется code_verifier, благодаря чему сервер может однозначно нас идентифицировать. Даже при перехвате данного хеша злоумышленником, он не сможет отправить POST запрос без знания настоящего code_verifier
Request
{
"authorization_code": "GWIXNQLX",
"code_verifier": "lqoctaktzu"
}
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5...",
"token_type": "Bearer",
"expires_in": 3600
}
Если хеш code_verifier не совпадёт с тем, что мы дали Authorization Server на первом шаге, то запрос на получение access_token отклонится
1 Шаг - Страница в веб браузере без backend/мобильном приложении (Public Client) перенаправляет пользователя на страницу авторизации и передаёт code_challenge
2 Шаг Пользователь (Resource Owner) осуществляет авторизацию на сервере авторизации (Authorization Server)
3 Шаг Authorization Server перенаправляет нас на redirect_uri с параметром authorization_code
4 Шаг Public Client через POST запрос передаёт authorization_code и code_verifier к Authorization Server
5 Шаг Authorization Server сверяет hash(code_verifier) с полученным на шаге code_challenge
6 Шаг Authorization Server отправляет POST ответ, в котором содержится access_token
7 Шаг Public Client может общаться с Resource Server по API, используя полученный access_token
Данный способ считается безопасным и используется на большинстве Public Client
Authorization Code Flow (Standard Flow) для Server Client
Данный способ аналогичен с Authorization Code Flow для публичных клиентов за исключением пути обмена authorization_code на access_token. Здесь фигурирует серверная сторона Client, которая с помощью client_secret может безопасно взаимодействовать с Authorization Server.
При регистрации приложения (Client) выдаётся идентификатор приложения (client_id) и секрет клиента (client_secret). client_id находится в открытом доступе, может передаваться между приложениями и является идентификатором для Authorization Server. Он передаётся на всех этапах взаимодействия с Authorization Server. client_secret нельзя передавать через открытые каналы и выдавать публичным частям приложения, секрет должен храниться только на сервере и передавать его можно только Authorization Server.
С помощью client_secret Authorization Server понимает, что запрос создания токена идёт из надёжного источника и токен можно выдать
Пример ссылки авторизации
https://auth.example.com/authorize?
response_type=code&
client_id=client_123&
redirect_uri=https://app.example.com/callback&
scope=profile email&
state=abc123
Пример ответа сервера
https://app.example.com/callback?code=GWIXNQLX&state=abc123
Пример запроса от Client (server)
Request:
{
"authorization_code": "GWIXNQLX",
"client_secret": "8AC664EAA0598C61813306EB3D5C064A7C...",
"client_id": "my_app-client"
}
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5...",
"token_type": "Bearer",
"expires_in": 3600
}
1 Шаг - Страница в веб браузере (Public Client) перенаправляет пользователя на страницу авторизации
2 Шаг Пользователь (Resource Owner) осуществляет авторизацию на сервере авторизации (Authorization Server)
3 Шаг Authorization Server перенаправляет нас на redirect_uri с параметром authorization_code
4 Шаг Authorization Code от Public Client оказывается на стороне server через прямое чтение uri параметров
5 Шаг Серверная сторона клиента Client (server) отправляет POST запрос к Authorization Server, содержащий client_secret и Authorization Code
6 Шаг Authorization Server сверяет client_secret и Access Code, после чего отправляет POST ответ, в котором содержится access_token
7 Шаг Client (secret) может общаться с Resource Server по API, используя полученный access_token
Наиболее часто используемый способ в OAuth 2.0 и OIDC - обеспечивает максимальную безопасность и надёжность
Device Authorization Flow (OAuth 2.0 Device Authorization Grant)
В случае, если с устройства проблематично произвести прямую аутентификацию, применяется Device Authorization Flow. Например, если нам нужно авторизоваться со Smart TV. В таком случае устройство не получает доступ напрямую, а просит пользователя авторизоваться со своего устройства, но с определённым кодом, который указан на экране, либо диктуется умным устройством.
После перехода по ссылке происходит стандартный процесс авторизации, но access_token не передаётся через redirect. Вместо этого устройство, которому нужно пройти авторизацию, постоянно опрашивает Authorization Server на наличие access_token для него.
Изначально запрашивается код через POST запрос с указание client_id и scope
{
"client_id": "client_123",
"scope": "read wirte email"
}
Response:
{
"device_code": "1895dac633dce9591866a4a2db59e408",
"user_code": "18301",
"redirect_uri": "sber.ru/auth"
}
После чего происходит polling на Authorization Sever sber.ru/token:
{
"device_code": "1895dac633dce9591866a4a2db59e408"
}
Ответ зависит от статуса подтверждения кода
При подтверждении:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5...",
"token_type": "Bearer",
"expires_in": 3600
}
Ошибки:
authorization_pending, slow_down, access_denied, expired_token
Далее происходит авторизация через браузер по указанной ссылке с использованием DC
1 Шаг - Устройство (Device) запрашивает у Authorization Server данные для авторизации с указанием client_id и запрашиваемых прав
2 Шаг Authorization Server присылает Device Code - то, что используется в качестве client_secret и однозначно идентифицирует устройство, User Code - то, что пользователь вводит в браузере, в нашем случае 18301. А также ссылка, по которой необходимо перейти пользователю (Resource Owner) для авторизации - verification_uri
3 Шаг Device выводит данные для начала процесса авторизации - ссылку и User Code
4 Шаг После вывода информации, Device начинает опрашивать Authorization Server с заданной периодичностью на наличие подтверждения, после подтверждения будет выдан Access Token
5 Шаг Resource Owner вводит данные от Device в браузере
6 Шаг Происходит авторизация на стороне Authorization Server
7 Шаг Authorization Server сопоставляет введённый user_code с соответствующим device_code и подтверждает сессию.
8 Шаг При следующей итерации Шага 4, Device получает в ответ AT
9 Шаг Device может общаться с Resource Server по API, используя полученный access_token
Считается безопасным способом авторизации и аутентификации пользователя для умных устройств
Client Credentials Flow (Service accounts roles)
Не всегда требуется авторизовываться от имени Resource Owner. В некоторых случаях OAuth 2.0 работает только между двумя приложениями. К примеру, для выставления счетов стороннее приложение может использовать API ФНС. В таком случае используется простой POST запрос b2b с передачей client_id и client_secret:
{
"grant_type": "client_credentials"
"client_id": "client_abc"
"client_secret": "5c6eeebe2a0b5abd1992d262f8a5daa0"
"scope": "read:invoices write:payments"
}
В ответ сразу же приходит access_token, без refresh_token т.к. получение access_tokena максимально простое и быстрое
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR...",
"token_type": "Bearer",
"expires_in": 3600
}
1 Шаг - Client (server) отправляет client_secret и client_id
2 Шаг Authorization Server возвращает Access Token
3 Шаг Client (server) может общаться с Resource Server по API, используя полученный access_token
OIDC CIBA Grant
Аутентификация происходит через подтверждение с доверенного устройства. Например, подтверждение через пуш уведомление на смартфоне
Refresh Flow
Является вспомогательной частью Authorization Flow.
В случае, если Authorization Server поддерживает обновление access_token без повторной процедуры аутентификации, помимо access_token клиенту (Client) выдаётся refresh_token, который имеет более длительный срок жизни. Когда срок действия access_token истекает, refresh_token может быть использован для получения нового access_token, а в некоторых случаях и нового refresh_token. Таким образом процесс авторизации происходит без помощи пользователя и может длиться значительно дольше жизни access_token. Работает по аналогии с Client Credentials Flow, но вместо client_secret использует refresh_token
1 Шаг Client (server) отправляет refresh_token и client_id
2 Шаг Authorization Server возвращает Access Token
3 Шаг Client (server) может общаться с Resource Server по API, используя новый access_token
Безопасный способ продления жизни access_token, используется повсеместно
mTLS все шаги